﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace StepDX
{
    public partial class Game : Form
    {
        /// <summary>
        /// The DirectX device we will draw on
        /// </summary>
        private Device device = null;
        /// <summary>
        /// Height of our playing area (meters)
        /// </summary>
        private float playingH = 4;
        /// <summary>
        /// Width of our playing area (meters)
        /// </summary>
        private float playingW = 32;
        /// <summary>
        /// Vertex buffer for our drawing
        /// </summary>
        private VertexBuffer vertices = null;
        /// <summary>
        /// The background image class
        /// </summary>
        private Background background = null;
        /// <summary>
        /// The number of stars on the level 
        /// </summary>
        private int numStars = 0;
        private int numStarsCollected = 0;
        /// <summary>
        /// What the last time reading was
        /// </summary>
        private long lastTime;
        /// <summary>
        /// What the initial time reading was
        /// </summary>
        private long startTime;
        /// <summary>
        /// A stopwatch to use to keep track of time
        /// </summary>
        private System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();

        

        /// <summary>
        /// All of the polygons that make up our world
        /// </summary>
        List<Polygon> world = new List<Polygon>();

        /// <summary>
        /// Our player sprite
        /// </summary>
        GameSprite player = new GameSprite();

        
        /// <summary>
        /// The collision testing subsystem
        /// </summary>
        Collision collision = new Collision();

        private GameSounds gameSound;






        /// <summary>
        /// Access to the maximum playing width
        /// </summary>
        public virtual float gameWidth { get { return playingW; } }

        /// <summary>
        /// A variable for keepting track of score font
        /// </summary>
        private Microsoft.DirectX.Direct3D.Font font;

        /// <summary>
        /// A variable for keeping track of score
        /// </summary>
        private int score = 0;

        // Keep track of whether winning dialog box already displayed
        bool playerWon = false;

        public Game()
        {
            InitializeComponent();

            if (!InitializeDirect3D())
                return;

            gameSound = new GameSounds(this);

            vertices = new VertexBuffer(typeof(CustomVertex.PositionColored), // Type of vertex
                                        4,      // How many
                                        device, // What device
                                        0,      // No special usage
                                        CustomVertex.PositionColored.Format,
                                        Pool.Managed);

            AddObstacle(0f, playingW, 0f, 1f, Color.Peru);
            //AddObstacle(2, 3, 1.7f, 1.9f, Color.Crimson);
            //AddObstacle(4, 4.2f, 1, 2.1f, Color.Coral);
            //AddObstacle(5, 6, 2.2f, 2.4f, Color.BurlyWood);
            //AddObstacle(5.5f, 6.5f, 3.2f, 3.4f, Color.PeachPuff);
            //AddObstacle(6.5f, 7.5f, 2.5f, 2.7f, Color.Chocolate);

            AddStar(2, 1);
            AddStar(3, 2);
            AddStar(5, 3.4f);
            AddStar(10, 2);
            AddStar(12, 1);
            

            AddStar(31f, 10f);
            AddStar(30f, 10f);
            AddStar(31.5f, 14.5f);
            AddStar(.5f, 4.5f);
            AddStar(.5f, 5.5f);
            AddStar(.5f, 6.5f);

            AddStar(5.25f, 2f);
            AddStar(5.25f, 6.5f);

            AddStar(7.85f, 4f);
            AddStar(9.4f, 4.5f);
            AddStar(9.4f, 7.5f);
            AddStar(13.4f, 5f);
            
            AddStar(15.4f, 6f);
            AddStar(16.4f, 6f);
            AddStar(17.4f, 6f);

            AddStar(14.5f, 9f);

            AddStar(15.4f, 3f);
            AddStar(16.4f, 2.5f);
            AddStar(17.4f, 3f);

            AddStar(20.6f, 1.7f);
            AddStar(20.6f, 3.5f);
            AddStar(20.6f, 8.5f);

            AddStar(24.6f, 3f);
            AddStar(23.9f, 4.5f);
            AddStar(25.9f, 4f);

            AddStar(27.7f, 2f);
            AddStar(28.6f, 2f);

            AddStar(31f, 2f);
            AddStar(31.5f, 2f);
            AddStar(31f, 2.5f);
            AddStar(31.5f, 2.5f);

            AddStar(28.4f, 6f);


            AddTrampoline(3.5f, 2.0f, 1.0f, .1f, .8f);
            AddTrampoline(2.5f, 4.5f, 1.0f, .1f, .9f);
            AddTrampoline(20.5f, 4f, .5f, .1f, .9f);
            AddTrampoline(31f, 4f, 1f, .1f, 1.4f);
            AddStar(2.6f, 9);


            AddSpaceBlock(5f, 3f, .8f, .4f);
            AddSpaceBlock(7.5f, 4.5f, 1f, .3f);
            AddSpaceBlock(10.5f, 4.5f, 1f, .3f);
            AddSpaceBlock(13f, 6f, 1f, .3f);
            AddSpaceBlock(17f, 2.5f, 1f, .3f);
            AddSpaceBlock(15f, 5f, 3f, .3f);
            AddSpaceBlock(23.5f, 4f, 1f, .3f);
            AddSpaceBlock(25.5f, 3.5f, 1f, .3f);
            AddSpaceBlock(30f, 6.5f, 1f, .3f);
            AddSpaceBlock(15f, 2.5f, 1f, .3f);
            AddSpaceBlock(27.5f, 1f, 1.6f, .6f);
            AddSpaceBlock(20.5f, 2.5f, .5f, .3f);


            AddMovingPlatform(12f, 1f, 2f, .3f, Color.Indigo, 2f);
            AddMovingPlatform(28f, 5f, 1f, .3f, Color.Indigo, 1f);

            /*Platform platform = new Platform();
            platform.AddVertex(new Vector2(3.2f, 2));
            platform.AddVertex(new Vector2(3.9f, 2));
            platform.AddVertex(new Vector2(3.9f, 1.8f));
            platform.AddVertex(new Vector2(3.2f, 1.8f));
            platform.Color = Color.CornflowerBlue;
            world.Add(platform);*/

            /*Texture texture = TextureLoader.FromFile(device, "../../stone08.bmp");
            PolygonTextured pt = new PolygonTextured();
            pt.Tex = texture;
            pt.AddVertex(new Vector2(4.0f, 1.9f));
            pt.AddTex(new Vector2(0, 1));
            pt.AddVertex(new Vector2(4.9f, 1.9f));
            pt.AddTex(new Vector2(0, 0));
            pt.AddVertex(new Vector2(4.9f, 1.8f));
            pt.AddTex(new Vector2(1, 0));
            pt.AddVertex(new Vector2(4.0f, 1.8f));
            pt.AddTex(new Vector2(1, 1));
            pt.Color = Color.Transparent;
            world.Add(pt);*/

            Texture spritetexture = TextureLoader.FromFile(device, "../../guy8.bmp");
            player.Transparent = true;
            player.P = new Vector2(0.5f, 1);
            player.Tex = spritetexture;
            player.AddVertex(new Vector2(-0.2f, 0));
            player.AddTex(new Vector2(0, 1));
            player.AddVertex(new Vector2(-0.2f, 1));
            player.AddTex(new Vector2(0, 0));
            player.AddVertex(new Vector2(0.2f, 1));
            player.AddTex(new Vector2(0.125f, 0));
            player.AddVertex(new Vector2(0.2f, 0));
            player.AddTex(new Vector2(0.125f, 1));
            player.Color = Color.Transparent;
            player.GameWidth = playingW;

            font = new Microsoft.DirectX.Direct3D.Font(device,  // Device we are drawing on
                    20,         // Font height in pixels
                    0,          // Font width in pixels or zero to match height
                    FontWeight.Bold,    // Font weight (Normal, Bold, etc.)
                    0,          // mip levels (0 for default)
                    false,      // italics?
                    CharacterSet.Default,   // Character set to use
                    Precision.Default,      // The font precision, try some of them...
                    FontQuality.Default,    // Quality?
                    PitchAndFamily.FamilyDoNotCare,     // Pitch and family, we don't care
                    "Arial");               // And the name of the font

            background = new Background(device, playingW, playingH);

            // Determine the last time
            stopwatch.Start();
            lastTime = stopwatch.ElapsedMilliseconds;
            startTime = (long)((double)stopwatch.ElapsedMilliseconds/(double)1000);

        }

        /// <summary>
        /// Initialize the Direct3D device for rendering
        /// </summary>
        /// <returns>true if successful</returns>
        private bool InitializeDirect3D()
        {
            try
            {
                // Now let's setup our D3D stuff
                PresentParameters presentParams = new PresentParameters();
                presentParams.Windowed = true;
                presentParams.SwapEffect = SwapEffect.Discard;

                device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
            }
            catch (DirectXException)
            {
                return false;
            }

            return true;
        }

        public void Render()
        {
            if (device == null)
                return;

            device.Clear(ClearFlags.Target, System.Drawing.Color.BurlyWood, 1.0f, 0);

            int wid = Width;                            // Width of our display window
            int hit = Height;                           // Height of our display window.
            float aspect = (float)wid / (float)hit;     // What is the aspect ratio?

            device.RenderState.ZBufferEnable = false;   // We'll not use this feature
            device.RenderState.Lighting = false;        // Or this one...
            device.RenderState.CullMode = Cull.None;    // Or this one...

            float widP = playingH * aspect;         // Total width of window
            float winCenter = player.P.X;
            if (winCenter - widP / 2 < 0)
                winCenter = widP / 2;
            else if (winCenter + widP / 2 > playingW)
                winCenter = playingW - widP / 2;

            float winPlayerY = player.P.Y;
            float yOffset = 0;
            if (winPlayerY > 2.3f) yOffset = winPlayerY - 2.3f;

            device.Transform.Projection = Matrix.OrthoOffCenterLH(winCenter - widP / 2,
                                                                  winCenter + widP / 2,
                                                                  yOffset, playingH + yOffset, 0, 1);

            //Begin the scene
            device.BeginScene();

            // Render the background
            background.Render();

            // Render the floor
            foreach (Polygon p in world)
            {
                p.Render(device);
            }

            // Render the sprite
            player.Render(device);

            // We'll add rendering code here...
            font.DrawText(null,     // Because I say so
            "Score: " + score,  // Text to draw
            new Point(10, 10),  // Location on the display (pixels with 0,0 as upper left)
            Color.LightCyan);   // Font color

            int starsLeft = numStars - numStarsCollected;
            // Display the number of stars left...
            font.DrawText(null,     // Because I say so
            "Stars Left: " + starsLeft,  // Text to draw
            new Point(10, 24),  // Location on the display (pixels with 0,0 as upper left)
            Color.LightGreen);   // Font color

            gameSound.Music();

            //End the scene
            device.EndScene();
            device.Present();
        }

        protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
                this.Close(); // Esc was pressed
            else if (e.KeyCode == Keys.Right || e.KeyCode == Keys.D)
            {
                Vector2 v = player.V;
                v.X = 1.5f;
                player.V = v;
            }
            else if (e.KeyCode == Keys.Left || e.KeyCode == Keys.A)
            {
                Vector2 v = player.V;
                v.X = -1.5f;
                player.V = v;
            }
            else if ((e.KeyCode == Keys.Space && (player.A.Y == 0 || player.V.Y == 0))  || (e.KeyCode == Keys.W && (player.A.Y == 0 || player.V.Y == 0) ))
            {
                Vector2 v = player.V;
                v.Y = 7;
                player.V = v;
                player.A = new Vector2(0, -9.8f);
                player.On = true;
            }
        }

        protected override void OnKeyUp(System.Windows.Forms.KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Right || e.KeyCode == Keys.Left  || e.KeyCode == Keys.D  || e.KeyCode == Keys.A)
            {
                Vector2 v = player.V;
                v.X = 0;
                player.V = v;
            }
        }

        /// <summary>
        /// Advance the game in time
        /// </summary>
        public void Advance()
        {
            // How much time change has there been?
            long time = stopwatch.ElapsedMilliseconds;
            long timeS = (long)((double)time / (double)1000);
            float delta = (time - lastTime) * 0.001f;       // Delta time in milliseconds
            lastTime = time;
            if (numStarsCollected >= numStars && playerWon == false)
            {
                playerWon = true;
                float elapsed = (float)time / 1000f;
                stopwatch.Stop();
                PlayerWins(elapsed, score);
            }

            while (delta > 0)
            {

                float step = delta;
                if (step > 0.05f)
                    step = 0.05f;

                float maxspeed = Math.Max(Math.Abs(player.V.X), Math.Abs(player.V.Y));
                if (maxspeed > 0)
                {
                    step = (float)Math.Min(step, 0.05 / maxspeed);
                }
                player.Advance(step);

                foreach (Polygon p in world)
                    p.Advance(step);

                foreach (Polygon p in world)
                {
                    if (collision.Test(player, p))
                    {
                        float depth = collision.P1inP2 ?
                                  collision.Depth : -collision.Depth;
                        // Change the color of the star upon collision
                        if (p.IsStar)
                        {

                            p.Color = Color.Red;

                            if (!p.Tagged)
                            {
                                int adjustScore = (200 - (int)(timeS - startTime));
                                if (adjustScore < 50)
                                {
                                    adjustScore = 50;
                                }
                                score += adjustScore;
                                numStarsCollected++;
                            }

                            p.Tagged = true;
                            
                        }
                        // Recoil if it's not a star... don't recoil if star
                        else
                        {
                            player.P = player.P + collision.N * depth;
                        }
                        Vector2 v = player.V;
                        // Doing this here so we can go directly through stars
                        if (collision.N.X != 0 && !p.IsStar)
                            v.X = 0;
                        if (collision.N.Y != 0 && !p.IsStar)
                        {
                            v.Y = 0;
                            player.On = false;
                        }

                        if ((collision.N.Y > 0 && p.IsTrampoline) && (player.P.Y >= p.VerticalHeight))
                        {
                            v.Y = 10 * p.Springiness;
                            player.On = false;
                        }

                        player.V = v;
                        player.Advance(0);
                    }
                }

                delta -= step;
            }
        }

        protected void AddObstacle(float left, float right, float bottom, float top, Color color)
        {
            Polygon poly = new Polygon();

            poly.AddVertex(new Vector2(left, top));
            poly.AddVertex(new Vector2(right, top));
            poly.AddVertex(new Vector2(right, bottom));
            poly.AddVertex(new Vector2(left, bottom));
            poly.Color = color;

            world.Add(poly);
        }

        protected void AddStar(float offsetX, float offsetY)
        {
            //Polygon poly = new Polygon();

            Texture texture = TextureLoader.FromFile(device, "../../stone08.bmp");
            PolygonTextured poly = new PolygonTextured(); 
            poly.Tex = texture;

            poly.AddVertex(new Vector2(offsetX + .079f, offsetY + .083f));
            poly.AddTex(new Vector2(0, 0));
            poly.AddVertex(new Vector2(offsetX + .006f, offsetY + .139f));
            poly.AddTex(new Vector2(0, 1));
            poly.AddVertex(new Vector2(offsetX + .095f, offsetY + .139f));
            poly.AddTex(new Vector2(1, 0));
            poly.AddVertex(new Vector2(offsetX + .124f, offsetY + .224f));
            poly.AddTex(new Vector2(1, 1));
            poly.AddVertex(new Vector2(offsetX + .151f, offsetY + .139f));
            poly.AddTex(new Vector2(0, 0));
            poly.AddVertex(new Vector2(offsetX + .241f, offsetY + .139f));
            poly.AddTex(new Vector2(0, 1));
            poly.AddVertex(new Vector2(offsetX + .169f, offsetY + .083f));
            poly.AddTex(new Vector2(1, 0));
            poly.AddVertex(new Vector2(offsetX + .193f, offsetY + .002f));
            poly.AddTex(new Vector2(1, 1));
            poly.AddVertex(new Vector2(offsetX + .124f, offsetY + .051f));
            poly.AddTex(new Vector2(0, 0));
            poly.AddVertex(new Vector2(offsetX + .052f, offsetY + .002f));
            poly.AddTex(new Vector2(0, 1));
            poly.AddVertex(new Vector2(offsetX + .079f, offsetY + .083f));
            poly.AddTex(new Vector2(1, 1));
            poly.Color = Color.Transparent;
            poly.IsStar = true;


            poly.Color = Color.Yellow;

            world.Add(poly);

            numStars++;
        }

        protected void AddTrampoline(float x, float y, float width, float height, float springiness)
        {
            Texture texture = TextureLoader.FromFile(device, "../../trampoline.bmp");
            PolygonTextured pt = new PolygonTextured();
            pt.Tex = texture;
            pt.AddVertex(new Vector2(x, y + height));
            pt.AddTex(new Vector2(0, 1));
            pt.AddVertex(new Vector2(x + width, y + height));
            pt.AddTex(new Vector2(0, 0));
            pt.AddVertex(new Vector2(x + width, y));
            pt.AddTex(new Vector2(1, 0));
            pt.AddVertex(new Vector2(x, y));
            pt.AddTex(new Vector2(1, 1));
            pt.Color = Color.Transparent;
            pt.IsTrampoline = true;
            pt.Springiness = springiness;
            pt.VerticalHeight = y;
            world.Add(pt);
        }

        protected void AddSpaceBlock(float x, float y, float width, float height)
        {
            Texture texture = TextureLoader.FromFile(device, "../../space_texture.bmp");
            PolygonTextured pt = new PolygonTextured();
            pt.Tex = texture;
            pt.AddVertex(new Vector2(x, y + height));
            pt.AddTex(new Vector2(0, 1));
            pt.AddVertex(new Vector2(x + width, y + height));
            pt.AddTex(new Vector2(0, 0));
            pt.AddVertex(new Vector2(x + width, y));
            pt.AddTex(new Vector2(1, 0));
            pt.AddVertex(new Vector2(x, y));
            pt.AddTex(new Vector2(1, 1));
            pt.Color = Color.Transparent;
            pt.IsTrampoline = true;
            pt.Springiness = 0;
            world.Add(pt);
        }


        protected void AddMovingPlatform(float x, float y, float width, float height, Color color, float Yrange)
        {
            Platform plat = new Platform();
            plat.AddVertex(new Vector2(x, y + height));

            plat.AddVertex(new Vector2(x + width, y + height));

            plat.AddVertex(new Vector2(x + width, y));

            plat.AddVertex(new Vector2(x, y));

            plat.Color = color;

            plat.Speed = 1;

            plat.MaxHeight = Yrange;

            world.Add(plat);
        }

        protected void PlayerWins(float elapsedTime, int score)
        {
            Form1 mForm = new Form1();
            mForm.label2.Text = "Final Score: " + score;
            mForm.label3.Text = "Time: " + elapsedTime + " seconds";
            mForm.ShowDialog();
            stopwatch.Start();
            return;
        }
    }
}
